package gui;

import java.net.URLClassLoader;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import bebetes.Bebete;

/**
 * Classe procédant au chargement dynamique de Bebetes à partir du répertoire
 * bebetes de la racine de l'archive des bebetes.
 * 
 * @author gnpt
 */
public class ChargeurDeBebetes {

	// Le chemin où chercher les bêbètes à charger.
	private static String chemin;
	private static java.io.File cheminDeRecherche;
	static {
		// FIXME Répertoire statique des bêbètes chargeables
		ChargeurDeBebetes.chemin = System.getProperty("user.dir", ".") + "/";
		ChargeurDeBebetes.cheminDeRecherche = new java.io.File(
				ChargeurDeBebetes.chemin);
	}
	// Le JFileChooser pour rechercher les bêbètes.
	private JFileChooser fileChooser;
	// Le LanceBebetes qui instancie le chargeur.
	private LanceBebetes parent;

	/**
	 * Constructeur du ChargeurDeBebetes.
	 * 
	 * @param parent
	 *            la JFrame instanciant cette classe.
	 */
	public ChargeurDeBebetes(LanceBebetes parent) {
		this.parent = parent;
	}

	/**
	 * Scanne le this.cheminDeRecherche à la recherche de fichiers class.
	 */
	public void chargerBebetes() {
		/* Demande du répertoire à l'utilisateur. */
		if (!this.demanderChemin())
			return;

		/* Filtre de fichier, on ne garde que les .class. */
		java.io.FileFilter classFilter = new java.io.FileFilter() {
			/**
			 * Filtre les fichiers .class uniquement.
			 * 
			 * @param file
			 *            le fichier à passer à travers le filtre.
			 * @return true si le fichier se nomme *.class.
			 */
			@Override
			public boolean accept(java.io.File file) {
				return file.getName().endsWith(".class");
			}
		};
		// Récupération des fichiers qui respectent le filtre ci-dessus.
		java.io.File classBebetes[] = ChargeurDeBebetes.cheminDeRecherche
				.listFiles(classFilter);

		// Si on a pas de .class dedans, message d'erreur.
		if (classBebetes == null || classBebetes.length < 1) {
			JOptionPane.showMessageDialog(this.parent,
					"Aucun fichier '.class' n'a été trouvé dans le dossier "
							+ chemin + ".", "Erreur",
					JOptionPane.WARNING_MESSAGE);
			return;
		}

		// On se prépare un ClassLoader sous le coude.
		java.net.URL loadPath = null;
		try {
			loadPath = new java.net.URL("file://"
					+ ChargeurDeBebetes.cheminDeRecherche.getAbsolutePath()
					+ "/");
		} catch (java.net.MalformedURLException e) {
			try {
				loadPath = ChargeurDeBebetes.cheminDeRecherche.toURI().toURL();
			} catch (java.net.MalformedURLException e1) {
				e1.printStackTrace();
			}
		}
		// Il faut donner au ClassLoader le chemin de recherche.
		ClassLoader cl = new java.net.URLClassLoader(
				new java.net.URL[] { loadPath }, this.getClass()
						.getClassLoader());
		Class<?> klass;
		// Il faut aussi stocker les classes chargées.
		java.util.ArrayList<Class<? extends bebetes.Bebete>> classesChargees = new java.util.ArrayList<Class<? extends bebetes.Bebete>>();

		// Chargeons (enfin !) les bêbètes.
		for (java.io.File f : classBebetes) {
			String fileName = f.getName();
			int prefixLength = fileName.length() - 6;
			String className = fileName.substring(0, prefixLength);
			try {
				klass = cl.loadClass(className);
			} catch (ClassNotFoundException e) {
				JOptionPane.showMessageDialog(this.parent,
						"Erreur lors du chargement de la classe " + className
								+ "\n" + e, "Erreur de chargement",
						JOptionPane.ERROR_MESSAGE);
				continue;
			} catch (NoClassDefFoundError e) {
				/* Le nom de la classe est incorrect ou incomplet */
				String errMsg = e.getMessage();
				int posFin = errMsg.indexOf(className, prefixLength);
				int posDebut = errMsg.lastIndexOf(' ') + 1;
				String pkg = errMsg.substring(posDebut, posFin);
				/* On récupère le nom du package de la classe depuis le message */
				className = pkg.replace('/', '.') + className;

				/* On construit un nouveau ClassLoader avec ce nouveau chemin */
				java.io.File newLoadFile = cheminDeRecherche.getAbsoluteFile();
				String pkgs[] = pkg.split("/");
				for (int i = 0; i < pkgs.length; i++) {
					newLoadFile = newLoadFile.getParentFile();
				}
				java.net.URL newLoadPath;
				try {
					newLoadPath = newLoadFile.toURI().toURL();
				} catch (java.net.MalformedURLException e2) {
					e2.printStackTrace();
					return;
				}

				cl = new URLClassLoader(new java.net.URL[] { newLoadPath }, cl);

				/* Et on recommence */
				try {
					klass = cl.loadClass(className);
				} catch (ClassNotFoundException e1) {
					JOptionPane.showMessageDialog(this.parent,
							"Erreur lors du chargement de la classe "
									+ className + "\n" + e1,
							"Erreur de chargement", JOptionPane.ERROR_MESSAGE);
					continue;
				}
			}

			// On vérifie qu'il s'agit bien de bêbètes.
			if (bebetes.Bebete.class.isAssignableFrom(klass)) {
				// On les ajoute à la liste, elles seront testées plus bas
				classesChargees.add(klass.asSubclass(bebetes.Bebete.class));
			}
		}

		/*
		 * Bon, à ce point on a une liste de bêbètes chargées en mémoire, mais
		 * non initialisées (voir Class.forName() vs ClassLoader.loadClass()).
		 * 
		 * Testons-les !
		 */
		junit.framework.TestResult tres;
		for (Class<? extends Bebete> c : classesChargees) {
			tres = autotest.AutoTest.run(c);

			gui.ResultatsTests res = new gui.ResultatsTests(c, tres);

			this.parent.ajouterResultatsTestsBebete(res);
		}
	}

	/**
	 * Instancie et affiche le JFileChooser de demande de chemin
	 */
	private boolean demanderChemin() {
		this.fileChooser = new JFileChooser(ChargeurDeBebetes.cheminDeRecherche);
		this.fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		int ret = this.fileChooser.showDialog(this.parent, "Charger");

		// Abandon de l'utilisateur ou erreur
		if (ret != JFileChooser.APPROVE_OPTION) {
			return false;
		}

		ChargeurDeBebetes.cheminDeRecherche = this.fileChooser
				.getSelectedFile();
		return true;
	}
}
